use super::ThreadRef;
use crate::fs::{FileMode, INodeExt};
use crate::prelude::*;
use rcore_fs::vfs::INode;
use std::ffi::CString;

/// Load an ELF file itself or a script's interpreter into a vector.
///
/// If the file is an executable binary, then just load this file.
/// If the file is an script text, then parse the shebang and load
/// the interpreter.
pub fn load_exec_file_to_vec(
    file_path: &str,
    current_ref: &ThreadRef,
) -> Result<(Option<String>, Vec<u8>)> {
    let file_buf = load_file_to_vec(&file_path, current_ref)?;
    let is_script = is_script_file(&file_buf);
    if is_script {
        // load interpreter
        let interpreter_path = parse_script_interpreter(&file_buf)?;
        if interpreter_path.starts_with("/host/") {
            return_errno!(
                EACCES,
                "libos doesn't support executing binaries from \"/host\" directory"
            );
        }
        let elf_buf = load_file_to_vec(&interpreter_path, current_ref)?;
        Ok((Some(interpreter_path), elf_buf))
    } else {
        Ok((None, file_buf))
    }
}

fn is_script_file(file_buf: &Vec<u8>) -> bool {
    file_buf.starts_with(&[b'#', b'!'])
}

// TODO: Support parsing interpreter arguments. e.g. `/usr/bin/python -u`
fn parse_script_interpreter(file_buf: &Vec<u8>) -> Result<String> {
    let mut start = 2; // after '#', '!'
    const MAX_LEN: usize = 127;

    // skip whitespaced between shebang and interpreter
    while (start < file_buf.len())
        && (file_buf[start] == ' ' as u8 || file_buf[start] == '\t' as u8)
    {
        start += 1;
    }

    let end = file_buf
        .iter()
        .take(MAX_LEN)
        .position(|&c| c == '\n' as u8)
        .ok_or_else(|| errno!(EINVAL, "script parsing error"))?;

    let interpreter = std::str::from_utf8(&file_buf[start..end])
        .map_err(|e| errno!(ENOEXEC, "failed to get the script interpreter"))?;
    trace!("script file using interpreter: {:?}", interpreter);
    Ok(interpreter.to_owned())
}

pub fn load_file_to_vec(file_path: &str, current_ref: &ThreadRef) -> Result<Vec<u8>> {
    let inode = current_ref
        .fs()
        .lock()
        .unwrap()
        .lookup_inode(file_path)
        .map_err(|e| errno!(e.errno(), "cannot find the file"))?;
    let file_mode = {
        let info = inode.metadata()?;
        FileMode::from_bits_truncate(info.mode)
    };
    if !file_mode.is_executable() {
        return_errno!(EACCES, "file is not executable");
    }
    if file_mode.has_set_uid() || file_mode.has_set_gid() {
        warn!(
            "set-user-ID and set-group-ID are not supportted, FileMode:{:?}",
            file_mode
        );
    }
    inode
        .read_as_vec()
        .map_err(|e| errno!(e.errno(), "failed to read the file"))
}
